//
// Copyright 2016 riteme
//

#include "Renderer.h"

#include <cmath>

#include <algorithm>
#include <utility>

namespace rsr {

Renderer::Renderer(Texture *texture) : _target(texture) {}

void Renderer::set_target(Texture *texture) {
    _target = texture;
}

void Renderer::clear(const Color4f &c) {
    _target->clear(c);
}

void Renderer::_map_ndc(const Vertex &v, int &x, int &y) const {
    x = (v.position.x + 1.0f) * 0.5f * _target->width();
    y = (-v.position.y + 1.0f) * 0.5f * _target->height();
}

int Renderer::_get_point_code(const Vertex &v) const {
    constexpr int LEFTCODE = 1;
    constexpr int RIGHTCODE = 2;
    constexpr int TOPCODE = 4;
    constexpr int BOTTOMCODE = 8;

    int code = 0;

    if (v.position.x < -1.0f)
        code |= LEFTCODE;
    else if (v.position.x > 1.0f)
        code |= RIGHTCODE;
    if (v.position.y < -1.0f)
        code |= BOTTOMCODE;
    else if (v.position.y > 1.0f)
        code |= TOPCODE;

    return code;
}

void Renderer::_rasterize_pixel(const int x, const int y, const Color4f c,
                                const Texcoord2f &tex) {
    if (0 <= x && x < _target->width() && 0 <= y && y < _target->height())
        (*_target)[x][y] += c;
}

void Renderer::_rasterize_pixel(const Vertex &v) {
    int x, y;
    _map_ndc(v, x, y);
    _rasterize_pixel(x, y, v.color, v.texcoord);
}

void Renderer::_rasterize_line(const Vertex &v1, const Vertex &v2) {
    // Line culling
    if (_get_point_code(v1) & _get_point_code(v2))
        return;

    int x1, y1, x2, y2;
    _map_ndc(v1, x1, y1);
    _map_ndc(v2, x2, y2);

    if (x1 == x2 && y1 == y2)
        _rasterize_pixel(x1, y1, v1.color, v1.texcoord);
    else if (x1 == x2) {
        float delta = fabs(y1 - y2);
        float step = 1.0f / delta;
        float r = 0.0f;

        for (int y = std::min(y1, y2); y <= std::max(y1, y2); y++) {
            draw_pixel(x1, y, interpolation(v1.color, v2.color, r),
                       interpolation(v1.texcoord, , v2.texcoord, r));
            r += step;
        }
    } else if (y1 == y2) {
        float delta = fabs(x1 - x2);
        float step = 1.0f / delta;
        float r = 0.0f;

        for (int x = std::min(x1, x2); x <= std::max(x1, x2); x++) {
            draw_pixel(x, y1, interpolation(v1.color, v2.color, r),
                       interpolation(v1.texcoord, v2.texcoord, r));
            r += step;
        }
    } else {
        // Bresenham Algorithm (integer version)
        int dy, y;
        if (x1 < x2) {
            dy = y1 < y2 ? 1 : -1;
            y = y1;
        } else {
            dy = y1 > y2 ? 1 : -1;
            y = y2;
        }

        // Use rx/ry to avoid sqrt in interpolations
        int delta_x = std::abs(x1 - x2);
        int delta_y = std::abs(y1 - y2);
        float stepx = 1.0f / delta_x;
        float stepy = 1.0f / delta_y;
        float rx = 0.0f;
        float ry = 0.0f;
        int e = 0;
        for (int x = std::min(x1, x2); x <= std::max(x1, x2); x++) {
            ry = rx;

            if (e < 0)
                _rasterize_pixel(x, y, interpolation(v1.color, v2.color, ry),
                                 interpolation(v1.texcoord, v2.texcoord, ry));
            else {
                while (e >= 0) {
                    e -= delta_x;
                    _rasterize_pixel(
                        x, y, interpolation(v1.color, v2.color, ry),
                        interpolation(v1.texcoord, v2.texcoord, ry));
                    ry += stepy;
                    y += dy;
                }  // while
            }

            e += delta_y;
            x += stepx;
        }
    }
}

void Renderer::_rasterize_scanline(const int x, const int y1, const Color4f c1,
                                   const Texcoord2f tex1, const int y2,
                                   const Color4f c2, const Texcoord2f tex2) {
    if (x < 0 || x >= _target->width())
        return;

    int _y1, _y2;
    Color4f _c1, _c2;
    Texcoord2f _tex1, _tex2;

    if (y1 < y2) {
        _y1 = y1, _c1 = c1, _tex1 = tex1;
        _y2 = y2, _c2 = c2, _tex2 = tex2;
    } else {
        _y1 = y2, _c1 = c2, _tex1 = tex2;
        _y2 = y1, _c2 = c1, _tex2 = tex1;
    }

    float delta = fabs(_y1 - _y2);
    float step = 1.0f / delta;
    float r;

    if (_y1 < 0) {
        r = -y1 / delta;
        _y1 = 0;
    } else
        r = 0.0f;

    if (_y2 >= _target->height())
        _y2 = _target->height() - 1;

    for (int y = _y1; y <= _y2; y++) {
        _rasterize_pixel(x, y, interpolation(_c1, _c2, r),
                         interpolation(_tex1, _tex2, r));

        r += step;
    }  // for
}

void Renderer::_draw_right_triangle(const Vertex &v1, const Vertex &v2,
                                    const Vertex &vr,
                                    const bool no_start = false) {
    int x1, y1, x2, y2, xr, yr;
    _map_ndc(v1, x1, y1);
    _map_ndc(v2, x2, y2);
    _map_ndc(vr, xr, yr);

    ASSERT(x1 == x2, "x1 should equal to x2");

    if (xr < x1)
        return;
    if (xr == x1) {
        if (!no_start) {
            _rasterize_scanline(x1, y1, v1.color, v1.texcoord, yr, vr.color,
                                vr.texcoord);
            _rasterize_scanline(x1, y2, v2.color, v2.texcoord, yr, vr.color,
                                vr.texcoord);
        }
    } else {
        float k1 = static_cast<float>(_ty - yr) / static_cast<float>(lx - xr);
        float k2 = static_cast<float>(_by - yr) / static_cast<float>(lx - xr);
        float _ty = std::min(y1, y2) + k1, _by = std::max(y1, y2) + k2;
        float step = 1.0f / (xr - x1);
        float r = step;

        if (!no_start)
            _rasterize_scanline(lx, y1, v1.color, v1.texcoord, y2, v2.color,
                                v2.texcoord);

        for (int x = x1 + 1; x <= xr; x++) {
            if (y1 < y2)
                _rasterize_scanline(
                    x, _ty + 0.5f, interpolation(v1.color, vr.color, r),
                    interpolation(v1.texcoord, vr.texcoord, r), _by + 0.5f,
                    interpolation(v2.color, vr.color, r),
                    interpolation(v2.texcoord, vr.texcoord, r));
            else
                _rasterize_scanline(
                    x, _ty + 0.5f, interpolation(v2.color, vr.color, r),
                    interpolation(v2.texcoord, vr.texcoord, r), _by + 0.5f,
                    interpolation(v1.color, vr.color, r),
                    interpolation(v1.texcoord, vr.texcoord, r));

            _ty += k1;
            _by += k2;
            r += step;
        }  // for
    }
}

void Renderer::_draw_left_triangle(const Vertex &v1, const Vertex &v2,
                                   const Vertex &vl,
                                   const bool no_start = false) {
    int x1, y1, x2, y2, xl, yl;
    _map_ndc(v1, x1, y1);
    _map_ndc(v2, x2, y2);
    _map_ndc(vl, xl, yl);

    ASSERT(x1 == x2, "x1 should equal to x2");

    if (xl < x1)
        return;
    if (xl == x1) {
        if (!no_start) {
            _rasterize_scanline(x1, y1, v1.color, v1.texcoord, yl, vl.color,
                                vl.texcoord);
            _rasterize_scanline(x1, y2, v2.color, v2.texcoord, yl, vl.color,
                                vl.texcoord);
        }
    } else {
        float k1 = static_cast<float>(_ty - yl) / static_cast<float>(lx - xl);
        float k2 = static_cast<float>(_by - yl) / static_cast<float>(lx - xl);
        float _ty = std::min(y1, y2) + k1, _by = std::max(y1, y2) + k2;
        float step = 1.0f / (xr - x1);
        float r = step;

        if (!no_start)
            _rasterize_scanline(lx, y1, v1.color, v1.texcoord, y2, v2.color,
                                v2.texcoord);

        for (int x = x1 - 1; x >= xl; x--) {
            if (y1 < y2)
                _rasterize_scanline(
                    x, _ty + 0.5f, interpolation(v1.color, vl.color, r),
                    interpolation(v1.texcoord, vl.texcoord, r), _by + 0.5f,
                    interpolation(v2.color, vl.color, r),
                    interpolation(v2.texcoord, vl.texcoord, r));
            else
                _rasterize_scanline(
                    x, _ty + 0.5f, interpolation(v2.color, vl.color, r),
                    interpolation(v2.texcoord, vl.texcoord, r), _by + 0.5f,
                    interpolation(v1.color, vl.color, r),
                    interpolation(v1.texcoord, vl.texcoord, r));

            _ty += k1;
            _by += k2;
            r += step;
        }  // for
    }
}

void Renderer::_rasterize_triangle(const Vertex &v1, const Vertex &v2,
                                   const Vertex &v3) {
    // Scanline algorithm
    const Point *points[] = { &v1, &v2, &v3 };
    std::sort(points, points + 3, [](const Point *a, const Point *b) {
        return a->position.x < b->positionx.x;
    });

    float r = (points[1]->position.x - points[0]->position.x) /
              (points[2]->position.x - points[0]->position.x);
    Vertex vmid;
    vmid.positionx.x = points[1]->positionx.x;
    vmid.positionx.y = (points[0]->positionx.y + points[2]->position.y) * r;
    vmid.color = interpolation(points[0]->color, points[2]->color, r);
    vmid.texcoord = interpolation(points[0]->texcoord, points[2]->texcoord, r);

    _draw_left_triangle(points[1], vmid, points[0]);
    _draw_right_triangle(points[1], vmid, points[2], true);
}

void Renderer::apply() {}

}  // namespace rsr
